-module(gproxy1).
-export([start/1,gateway/1,stop/1,rpc/2,change_be/2, change_code/2]).

-define(TIMEOUT, 3000).

%% TODO: 
%%  - balance: gloop([S1, S2, S3])
%%  - backend failover

%% start the server (backend)
start(Fun) ->
  spawn(fun() -> loop(Fun) end).

%% start the proxy (frontend)
gateway(Backend) ->
  spawn(fun() -> gloop(Backend) end).

%% stop gateway or backend
stop(Pid) -> 
  Pid ! stop.

%% Pid can be gateway or server 
rpc(Pid, Query) ->
  Pid ! {self(), Query},
  receive
    {Pid, crash} ->
      exit(rpc); 
    {Pid, Reply} ->
      Reply
  end.

log_msg(Type, Pid, Query, Why) ->
  case Type of
    err ->
      io:format("[E] Server: ~p, Query: ~p, Reason: ~p~n", [Pid, Query, Why]);
    done ->
      io:format("[*] Done. Server: ~p, Query: ~p, Reply: ~p~n", [Pid, Query, Why]);
    timeout ->
      io:format("[T] Server: ~p, Timeout after: ~p~n", [Pid, Query])
  end.

%% change the backend
%% send to the GATEWAY
change_be(Gw, Backend) ->
  Gw ! {Backend, be_change}.

%% function code change
%% send to the SERVER
change_code(Server, Fun1) ->
  Server ! {swap_code,Fun1}.

%% server loop
loop(Fun) ->
  receive
    stop ->
      void;
    {swap_code,Fun1} ->
      loop(Fun1);
    %% direct request
    {Client, Data} ->
      case catch Fun(Data) of
        {'EXIT', Why} ->
          log_msg(err, Client, Data, Why),
          Client ! {self(), crash},
          loop(Fun);
        Reply ->
          Client ! {self(), Reply},
          loop(Fun)
      end;
    %% request via gateway
    {Gw, Client, Data} ->
      case catch Fun(Data) of
        {'EXIT', Why} ->
          log_msg(err, Client, Data, Why),
          Gw ! {self(), Client, crash},
          loop(Fun);
        Reply ->
          Gw ! {self(), Client, ok, Reply},
          loop(Fun)
      end
  end.

%% gateway loop
gloop(Backend) ->
  receive
    stop ->
      void;
    {Backend1, be_change} ->
      gloop(Backend1);
    {Client, Data} ->
      Backend ! {self(), Client, Data},
      gloop(Backend);
    {Server, Client, crash} ->
      Client ! {self(), crash},
      gloop(Backend);
    {Server, Client, ok, Data} ->
      Client ! {self(), Data},
      gloop(Backend)
  end.
